#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>

#include "bloom_filter.h"
#include "type_routingtable.h"
#include "util.h"


static multicast_group_node* g_pRoutingTable = NULL; 

// Returns the pointer to the RoutingTable
multicast_group_node* GetRoutingTable()
{ 
    return g_pRoutingTable;
}

// Returns the multicast_group_node entry associated with the multicast group address.
// Returns NULL if not found.
multicast_group_node* FindMulticastGroup(u_int32_t multicast_group_addr) 
{ 
    multicast_group_node * mglIter = NULL;
    for (mglIter = g_pRoutingTable;
        mglIter != NULL;
        mglIter = mglIter->next)
    {
        if (mglIter->multicast_group_addr == multicast_group_addr)
            return mglIter;
    }
    
    return NULL;
}

// Inserts a multicast group entry into the routing table with associated LBF.
// If the group entry already exists, only the LBF is updated. 
multicast_group_node* InsertMulticastGroup(u_int32_t multicast_group_addr, bloom_filter_t LBF)
{
     multicast_group_node * TargetMulticastGroup = FindMulticastGroup(multicast_group_addr); 
     if (TargetMulticastGroup != NULL)
    { 
         copy_bloom_filter(TargetMulticastGroup->LBF, LBF); 
         return TargetMulticastGroup; 
     }
    else 
    { 
         TargetMulticastGroup = (multicast_group_node *) malloc(sizeof(multicast_group_node)); 
         if (TargetMulticastGroup == NULL)
        { 
             fprintf(stderr, "type_routingtable.c: cannot allocate memory\n"); 
             exit(-1); 
         }

         TargetMulticastGroup->multicast_group_addr = multicast_group_addr; 
        TargetMulticastGroup->neighborHead = NULL;
        TargetMulticastGroup->next = g_pRoutingTable;
        g_pRoutingTable = TargetMulticastGroup;
         copy_bloom_filter(TargetMulticastGroup->LBF, LBF); 
     }                  
}

// Returns the multicast neighbor entry with matching group and neighbor address.
// Returns NULL if not found.  (If the timer is expired, the node is still returned.)
multicast_neighbor_node* FindMulticastNeighbor(u_int32_t multicast_group_addr, u_int32_t multicast_neighbor_addr) 
{
    multicast_group_node * TargetMulticastGroup;
    time_t now;

    TargetMulticastGroup = FindMulticastGroup(multicast_group_addr);         
    if(TargetMulticastGroup == NULL) 
    { 
        return NULL; 
    }

    multicast_neighbor_node * nbrIter = NULL; 
    for (nbrIter = TargetMulticastGroup->neighborHead; 
        nbrIter != NULL;
        nbrIter = nbrIter->next)
    {
        if (nbrIter->multicast_neighbor_addr == multicast_neighbor_addr)
        { 
            now = time(NULL); 
            if (now > nbrIter->expire) 
                return NULL; 
            else 
                return nbrIter; 
        }
    }

    return NULL; 
}

// Insert a multicast_neighbor_node with associated multicast_group_addr and multicast_neighbor_addr.
// If the entry already exists, only the FBF and the expiration timer are updated.
multicast_neighbor_node* InsertMulticastNeighbor(u_int32_t multicast_group_addr, u_int32_t multicast_neighbor_addr, bloom_filter_t FBF)
{
    multicast_group_node * TargetMulticastGroup;
    time_t now;

    TargetMulticastGroup = FindMulticastGroup(multicast_group_addr);         
    if(TargetMulticastGroup == NULL) 
    {
        fprintf(stderr, "type_routingtable.c: Trying to add neighbor to nonexistent group.\n"); 
        exit(-1); 
    }

    multicast_neighbor_node * TargetMulticastNeighbor;
    TargetMulticastNeighbor = FindMulticastNeighbor(multicast_group_addr, multicast_neighbor_addr);

    if (TargetMulticastNeighbor != NULL)
    {
        TargetMulticastNeighbor->expire = time(NULL) + TYPE_RT_TIMEOUT;
        copy_bloom_filter(TargetMulticastNeighbor->FBF, FBF);
        return TargetMulticastNeighbor;
    }
    else
    {
        TargetMulticastNeighbor = (multicast_neighbor_node *) malloc(sizeof(multicast_neighbor_node)); 
        TargetMulticastNeighbor->multicast_neighbor_addr = multicast_neighbor_addr;
        TargetMulticastNeighbor->expire = time(NULL) + TYPE_RT_TIMEOUT;
        init_bloom_filter(TargetMulticastNeighbor->ABF);        // TODO: How do we handle this?
        copy_bloom_filter(TargetMulticastNeighbor->FBF, FBF);
        TargetMulticastNeighbor->next = TargetMulticastGroup->neighborHead ;
        TargetMulticastGroup->neighborHead = TargetMulticastNeighbor; 
        return TargetMulticastNeighbor;
    }
}

// Aggregate the ABF for each neighbor in every multicast group.
void UpdateABFs()
{
    // For every group
    multicast_group_node* mglIter = NULL;
    for (mglIter = g_pRoutingTable;
        mglIter != NULL;
        mglIter = mglIter->next)
    {
        // For every neighbor
        multicast_neighbor_node * nbrCurrentIter = NULL;
        for (nbrCurrentIter = mglIter->neighborHead;
            nbrCurrentIter != NULL;
            nbrCurrentIter = nbrCurrentIter->next)
        {
            bloom_filter_t ABF;
            copy_bloom_filter(ABF, mglIter->LBF);

            // Aggregate every other neighbor's Bloom Filter
            multicast_neighbor_node * nbrIter = NULL;
            time_t now = time(NULL); 
            for (nbrIter = mglIter->neighborHead;
                nbrIter != NULL;
                nbrIter = nbrIter->next)
            {
                if ((nbrCurrentIter != nbrIter) &&
                    (nbrIter->expire > now))
                {
                    add_bloom_filter(ABF, nbrIter->FBF);
                }
            }

            // Copy this aggregated ABF into the neighbor entry.
            copy_bloom_filter(nbrCurrentIter->ABF, ABF);
        }
    }
}

void DumpTypeCastRoutingTable(int fd) 
{
    struct timeval now;     
    gettimeofday(&now, NULL);
    fprintf((FILE *) fd, "TypeCast Routing Table for %s - %s", ip_to_string(get_local_ip_address()),  ctime(&now.tv_sec)); 
    multicast_group_node * mglIter = NULL;
    for (mglIter = g_pRoutingTable;
        mglIter != NULL;
        mglIter = mglIter->next)
    {
        fprintf((FILE *) fd, "group = %s:\n", ip_to_string(mglIter->multicast_group_addr)); 
        fprintf((FILE *) fd, "\tLBF = %s\n", bloom_filter_to_string(mglIter->LBF));  
        multicast_neighbor_node * nbrCurrentIter = NULL;
        for (nbrCurrentIter = mglIter->neighborHead;
            nbrCurrentIter != NULL;
            nbrCurrentIter = nbrCurrentIter->next)
        {
            fprintf((FILE *) fd, "\tneighbor=%s\n\t\tFBF=%s\n\t\tABF=%s\n", ip_to_string(nbrCurrentIter->multicast_neighbor_addr), 
                bloom_filter_to_string(nbrCurrentIter->FBF), bloom_filter_to_string(nbrCurrentIter->ABF)); 
        }
    }        
                     
}
